#ifndef UTILS_HPP
#define UTILS_HPP
#include <iostream>
#include <sys/time.h>
#include <string.h>
#include <opencv2/opencv.hpp>
#include <cuda_runtime.h>
#include "preprocess_kernel.cuh"

#define CHECK(call)                                                          \
    {                                                                        \
        const cudaError_t error = call;                                      \
        if (error != cudaSuccess)                                            \
        {                                                                    \
            printf("ERROR: %s:%d,", __FILE__, __LINE__);                     \
            printf("code:%d,reason:%s\n", error, cudaGetErrorString(error)); \
            exit(1);                                                         \
        }                                                                    \
    }

namespace CPUCUDATools
{
    inline double get_time()
    {
        struct timeval tp;
        struct timezone tzp;
        int i = gettimeofday(&tp, &tzp);
        return ((double)tp.tv_sec + (double)tp.tv_usec * 1.e-6) * 1000;
    }

    void verify_result(float *cuda_res, cv::Mat &opecv_res, const int res_w, const int res_h)
    {
        cv::Mat_<float> b(res_h, res_w, cuda_res);
        cv::Mat_<float> g(res_h, res_w, cuda_res + (res_h * res_w));
        cv::Mat_<float> r(res_h, res_w, cuda_res + 2 * (res_h * res_w));
        cv::Mat gpu3c;
        cv::merge(std::vector<cv::Mat>{b, g, r}, gpu3c);
        gpu3c.convertTo(gpu3c, CV_8U);

        auto pg = gpu3c.data;
        auto pc = opecv_res.data;
        int count = gpu3c.rows * gpu3c.cols * 3;
        int count0 = 0;
        int count1 = 0;
        int count2 = 0;
        int count3 = 0;
        int count5 = 0;
        for (int i = 0; i < count; ++i, ++pg, ++pc)
        {

            int gval = *pg;
            int cval = *pc;
            int absdiff = abs(gval - cval);
            if (absdiff > 0)
                count0++;

            // if (absdiff > 0)
            // {
            //     printf("gval=%d,cval=%d\n", gval, cval);
            //     count0++;
            // }

            if (absdiff > 1)
                count1++;

            if (absdiff > 2)
                count2++;

            if (absdiff > 3)
                count3++;

            if (absdiff > 5)
                count5++;
        }
        printf("absdiff >0 = %d, >1 = %d, >2 = %d, >3 = %d, >5 = %d\n",
               count0, count1, count2, count3, count5);

        cv::Mat absdiff_image;
        cv::absdiff(gpu3c, opecv_res, absdiff_image);

        absdiff_image.convertTo(absdiff_image, CV_8U, 30);
        cv::imwrite("images/gpu_resize.png", gpu3c);
        cv::imwrite("images/opencv_resize.png", opecv_res);
    }

    cv::Mat opencv_resize(const std::string &input_path, int width, int height, const int run_times)
    {

        double otime;
        cv::Mat input_image, src_resize_image;
        input_image = cv::imread(input_path);

        otime = get_time();
        for (int i = 0; i < run_times; i++)
            cv::resize(input_image, src_resize_image, cv::Size(width, height));
        std::cout << "opencv default resize:" << (get_time() - otime) / run_times << "ms" << std::endl;

        // cv::imwrite("images/opencv_resize.png", src_resize_image);
        return src_resize_image;
    }

    void cuda_resize(float *resize_data_host, const std::string &input_path, int width, int height, const int run_times)
    {
        double gtime;
        cudaStream_t stream;

        // 读取原始图像，并计算该图像的总字节数和resize后图片的字节数
        cv::Mat input_image = cv::imread(input_path);
        size_t image_bytes = (input_image.rows * input_image.cols * 3) * sizeof(uint8_t);
        float resize_bytes = (height * width * 3) * sizeof(float); // 双线性差值是float计算，所以申请float的字节空间

        // 申请cuda内存用来上传图片和申请resize后图片的内存
        uint8_t *image_data_device = nullptr;
        float *resize_data_device = nullptr;
        CHECK(cudaMalloc((uint8_t **)&image_data_device, image_bytes));
        CHECK(cudaMemset(image_data_device, 0, image_bytes));

        CHECK(cudaMalloc((float **)&resize_data_device, resize_bytes));
        CHECK(cudaMemset(resize_data_device, 0, resize_bytes));

        // 上传图片到cuda，注意，这里使用了cudaStream(其实用不用都行，这里只是复习一下用法)
        cudaStreamCreate(&stream);
        cudaMemcpyAsync(image_data_device, input_image.data, image_bytes, cudaMemcpyHostToDevice, stream);

        gtime = get_time();
        for (int i = 0; i < run_times; i++)
            CUDAKernel::resize_bilinear(image_data_device, input_image.cols * 3, input_image.cols, input_image.rows,
                                        resize_data_device, width, height, stream);
        std::cout << "cuda bilinear resize:" << (get_time() - gtime) / run_times << "ms" << std::endl;

        // 下面的操作是把resize后的结果传输到cpu上，如果是推理的话不需要该步骤，
        // 这里是因为要和opencv的resize对比
        cudaMemcpyAsync(resize_data_host, resize_data_device, resize_bytes, cudaMemcpyDeviceToHost, stream);

        cudaStreamSynchronize(stream);
        cudaStreamDestroy(stream);
        cudaFree(image_data_device);
        cudaFree(resize_data_device);
    }
};
#endif // UTILS_HPP
